Skip to content

test(ui): add UserButton connected integration test - #9191

Open
alexcarpenter wants to merge 24 commits into
carp/account-button-controllerfrom
carp/account-button-integration
Open

test(ui): add UserButton connected integration test#9191
alexcarpenter wants to merge 24 commits into
carp/account-button-controllerfrom
carp/account-button-integration

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jul 17, 2026

Copy link
Copy Markdown
Member

Description

Stacked on #9185. Covers the connected UserButton through the real view and the real controller against a mocked Clerk, and settles the popover behaviour that coverage exposed.

One flow, one machine. The popover's open state and the one action in flight are the same flow: an action that ends the interaction closes the surface, so they settle together or not at all. user-button.machine.ts holds both instead of two useStates in the container. Re-entry, clearing busy, and closing on success stop being hand written there: RUN is simply unhandled while busy, and busy is only reachable from open. Dismissing the popover mid action now abandons the result rather than letting it land in a surface that is already gone.

The surface holds still while an action runs. The view renders the controller the action started from. setActive swaps the active organization while its promise is in flight, so the live controller would otherwise rearrange the popup under the pointer: the header renaming itself, the check jumping rows, Invite coming and going as the permission is re-read. The result lands in one step when the action settles.

The popover closes behind whatever it opens. A modal or another page takes over from there, so there is nothing left for the popover to show. Left up, it would sit over the very surface it just opened.

A failure leaves the popover open so the row can be clicked again. Nothing reports what went wrong yet; the error surface is its own change.

mode reaches the connected button. Organizations turned off at the instance leave nothing for an organization surface to lead with or to list, so the button is the account's whatever mode asked for. clerk-js withholds its own <OrganizationSwitcher> at the mount boundary, and nothing mounts this one, so the gate lives in the container.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other: test coverage

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 7, 2026 3:33pm
swingset Ready Ready Preview Aug 7, 2026 3:33pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 970ea4d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a7f3fe3-0f9b-4531-87b3-6dfbb33b079e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 17, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 17, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

This PR adds integration tests for the AccountButton component with no production code changes. The empty changeset confirms no packages are affected, making this a low-risk test-only addition.

You can customize Macroscope's approvability policy. Learn more.

@vercel
vercel Bot temporarily deployed to Preview – clerk-js-sandbox July 20, 2026 21:20 Inactive
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-integration branch from 22e6748 to 4f890d8 Compare July 30, 2026 18:38
@alexcarpenter alexcarpenter changed the title test(ui): add AccountButton connected integration test test(ui): add UserButton connected integration test Jul 30, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9191

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9191

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9191

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9191

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9191

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9191

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9191

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9191

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9191

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9191

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9191

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9191

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9191

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9191

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9191

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9191

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9191

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9191

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9191

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9191

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9191

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9191

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9191

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9191

commit: 970ea4d

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-07T15:37:47.748Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 970ea4d.

Render the connected UserButton against stubbed Clerk hooks and drive it through
the popover: organization selection, account switching, sign out, accepting
invitations and suggestions, single-session mode, navigation actions, busy and
loading states, and the paging sentinel.
Every other action — switching account, signing out of one, joining a
suggested or invited workspace — now resolves back into an open popover so
the result is visible where it happened. The swingset prototypes fake the
round trip they make against Clerk, so the spinner and stood-down rows are
demonstrable without a running app.
The sizes track the Icon scale (sm 14px, md 16px) so a spinner can stand in
for the icon it replaces, and the UserButton's trailing column is now one
slot the width of the menu button, so the spinner, the active check, and the
menu all sit on the same centre line.
The trigger carried the avatar alone. It now names what is active beside it — the
organization and its plan wherever one heads the trigger, the account otherwise —
behind `showLabel`, which defaults on.

Badge's `neutral` color was unreadable in both schemes: its fill is a 900 and its
text token is a text color, not an on-fill one. It now rides the same black/white
scrim the button's neutral fill does.
…ccount

The trigger and the popup's header now always name the same workspace. `combined`
carries both switchers, so `modePriority` picks which one it leads with: the active
organization by default, the account with `modePriority="user"`. Both are still listed
either way.
…ser fixture

The controller now reads hasOrganizations off the user resource, so the mocked user needs the field the real one has.
`setActive` swaps the active organization while its promise is still in
flight, so the popup rearranged mid-action: the header renamed itself, the
check jumped rows, and Invite came and went as the permission was re-read.
The connected component now snapshots the controller when an action starts
and renders that until it settles, so the result lands in one step.

Two smaller faults fell out of the same interaction:

- The spinner waited out a delay window before appearing, and the check
  raced ahead of it. Every action here is a network round trip, so there
  is nothing to debounce: `useSpinDelay` takes `delay: 0` and shows the
  value in the same pass, with `minDuration` still steadying it.

- A row going busy swapped its host element from `<button>` to `<div>`,
  remounting the subtree and dropping the avatar back to its initials for
  the length of the action. A row that stands down now stays the button it
  was, disabled, and `Avatar.Image` resolves a browser-cached `src` in a
  layout effect so neither a remount nor a swap flashes the fallback.
The popover stayed up behind the surface it opened. Managing, inviting, creating an
organization, and adding an account now close it on the way out, whether they open a modal
or navigate.
The connected test drives the real controller against a mocked Clerk, which is what
makes it worth having and also what makes it slow. Cases that only ever asserted what
the popover renders now sit in the view test, leaving the connected one to prove the
layers compose. Also covers `hidePersonal` reaching the popover through the container.
UserButtonProps picked only modePriority off the root, so the connected component was hard-wired to the combined surface and the orgs/user modes were reachable only by composing UserButtonView directly.
Presses a custom row on the connected UserButton and checks the app's
callback runs and the popover closes behind it.
An instance with organizations turned off has none to lead with or list,
so the button is the account's whatever `mode` asked for — `orgs` would
otherwise render an empty shell of a switcher. clerk-js withholds its own
OrganizationSwitcher at the mount boundary, which an app importing this
one never crosses, so the gate lives in the component.
The popover's open state and the one action in flight are the same flow, so
they now live in one machine instead of two useStates. Re-entry, clearing
busy, and closing on success stop being hand-written in the container: RUN is
simply unhandled while busy, and busy is only reachable from open.

Dismissing the popover mid-action now abandons the result rather than letting
it land in a surface that is already gone.
Follows the view's rename of `'orgs'` to `'organization'`.

Also corrects the integration suite's opening comment, which attributed
close-on-success to the container and had the navigation case backwards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant